package de.mmis.core.publishsubscribe.filter;

import java.util.HashMap;
import java.util.Map;

import de.mmis.core.base.DeserializableAnnotations.DeserializationMethod;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.serializer.DeserializableException;
import de.mmis.core.base.abstracttree.serializer.Serializable;
import de.mmis.core.base.event.Event;

/**
 * represents an arbitrary value in a {@link AtomicFilter}. It can either be a
 * pure {@link Tree} or a reference to such expression in the Event data.
 * 
 * @author Martin Nyolt
 * 
 */
public class FilterValue implements Serializable {
	private String key = null;
	private Tree exp = null;
	private String id = null;

	/**
	 * A value of a specific key in an {@link Event}
	 * 
	 * @param key
	 *            the key in the Event
	 */
	public FilterValue(String key) {
		this.key = key;
	}

	/**
	 * Just the value given
	 * 
	 * @param expression
	 *            the value
	 */
	public FilterValue(Tree expression) {
		this.exp = expression;
	}

	/**
	 * A value of a specific key in the state of the device with the given id.
	 * 
	 * @param id
	 *            The id of the device
	 * @param key
	 *            The key of the value
	 */
	public FilterValue(String id, String key) {
		this.id = id;
		this.key = key;
	}

	/**
	 * @return the key
	 */
	public String getKey() {
		return key;
	}

	/**
	 * @return the exp
	 */
	public Tree getExp() {
		return exp;
	}

	/**
	 * @return the id
	 */
	public String getId() {
		return id;
	}

	/**
	 * @param eventData
	 * 
	 * @return the value according to the event, or <code>null</code> if the
	 *         value is not set.
	 */
	public Tree getValue(Map<String, Tree> eventData,
			HashMap<String, Map<String, Tree>> worldstate) {
		if (exp != null)
			return exp;

		if (id != null) {
			if (worldstate == null)
				return null;

			eventData = worldstate.get(id);
			if (eventData == null)
				return null;
		}

		return eventData.get(key);
	}

	@Override
	public Tree[] serialize(boolean explicit, Map<Object, String> refmap,
			ClassLoader classLoader) {
		Tree[] result;

		if (exp != null) {
			result = new Tree[2];
			result[0] = new LeafNode(":exp");
			result[1] = Tree.fromObject(exp);
		} else if (id == null) {
			result = new Tree[2];
			result[0] = new LeafNode(":ref");
			result[1] = Tree.fromObject(key);
		} else {
			result = new Tree[3];
			result[0] = new LeafNode(":state");
			result[1] = Tree.fromObject(id);
			result[2] = Tree.fromObject(key);
		}

		return result;
	}

	@Override
	public String getTypeIdentifier() {
		return "publishsubscribe/filter-value";
	}

	@DeserializationMethod
	public static FilterValue deserialize(Tree[] exps, ClassLoader cl,
			String id, Map<String, Object> idmap)
			throws DeserializableException {
		if (exps.length != 2 && exps.length != 3)
			throw new DeserializableException(null,
					"Accept only two or three parameters");

		Tree t = exps[0];
		if (new LeafNode(":exp").equals(t)) {
			Tree exp = exps[1].getAs(Tree.class);
			return new FilterValue(exp);
		}

		if (new LeafNode(":ref").equals(t)) {
			String key = exps[1].getAsString();
			return new FilterValue(key);
		}

		if (new LeafNode(":state").equals(t)) {
			String ID = exps[1].getAsString();
			String key = exps[2].getAsString();
			return new FilterValue(ID, key);
		}

		throw new DeserializableException(exps[0],
				"First S-Expression is neither :exp nor :ref nor :state");
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (!(obj instanceof FilterValue))
			return false;

		FilterValue o = (FilterValue) obj;

		if (o.exp == null) {
			if (exp != null || o.key == null)
				return false;
			return o.key.equals(key);
		}

		return o.exp.equals(exp);
	}

	@Override
	public String toString() {
		if (exp != null)
			return exp.toString();

		String result = "$" + key;

		if (id != null)
			result += "@" + id;

		return result;
	}

	@Override
	public int hashCode() {
		if (exp != null)
			return exp.hashCode();

		return key.hashCode();
	}
}
